<?php

class Paypal extends Page_Controller {
    private static $allowed_actions = array(
        'process',
        'cancel',
        'PreCompleteForm',
        'pre_complete',
        'complete',
        'notify'
    );

    function process() {
        if(SecurityToken::inst()->checkRequest($this->request)){
            $payment = PaypalPayment::get()->byID($this->request->param('ID'));
            if($payment && $payment->Status == 'Incomplete'){
                return $this->renderWith('PaypalTemplate');
            }
            else{
                return $this->redirect($payment->RedirectLink());
            }
        }
        
        return $this->httpError('404');
    }
    
    function notify(){
        // Check to see there are posted variables coming into the script
        if (!$this->request->isPOST()){
            die("Empty post variables");
        }
        
        $custom = $this->request->postVar('custom');
        $params = explode('-', $custom);
        if(count($params) != 2) {
            die("Invalid custom value");
        }
        
        $payment = PaypalPayment::get()->byID($params[0]);
        if(!$payment || $payment->AuthorisationCode != $params[1]) {
            die("Invalid custom validate");
        }
        
        $data = array(
            'TxnRef' => $this->request->requestVar('txn_id')
        );
        
        // Initialize the $req variable and add CMD key value pair
        $req = 'cmd=_notify-validate';
        // Read the post from PayPal
        foreach ($this->request->postVars() as $key => $value) {
            $value = urlencode(stripslashes($value));
            $req .= "&$key=$value";
        }
        // Now Post all of that back to PayPal's server using curl, and validate everything with PayPal
        // We will use CURL instead of PHP for this for a more universally operable script (fsockopen has issues on some environments)
        $url = PaypalPayment::config()->test_mode ? PaypalPayment::config()->test_url : PaypalPayment::config()->url;
        $account = PaypalPayment::config()->test_mode ? PaypalPayment::config()->test_account_email : PaypalPayment::config()->account_email;
        $curl_result = '';
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        
        $req = str_replace("&", "\n\n", $req);  // Make it a nice list in case we want to email it to ourselves for reporting
        
        if(!($curl_result = curl_exec($ch))) {
            $req .= "\n\nConnection error - ".curl_error($ch);
            $this->doPaymentLog($payment, "Paypal Payment Log - Connection error", $req);
            curl_close($ch);
            exit();
        }
            
        curl_close($ch);

        // Check that the result verifies
        if(strcmp($curl_result, "VERIFIED") == 0){
            $req .= "\n\nPaypal Verified OK";
        } else {
            $req .= "\n\nData NOT verified from Paypal!";
            $this->doPaymentLog($payment, "Paypal Payment Log - IPN interaction not verified", $req);
            exit();
        }
        
        /* CHECK THESE 4 THINGS BEFORE PROCESSING THE TRANSACTION, HANDLE THEM AS YOU WISH
          1. Make sure that business email returned is your business email
          2. Make sure there are no duplicate txn_id
          3. Make sure the payment amount/currency matches what you charge for items. (Defeat Price-Jacking)
          4. Make sure that the transaction payment status is completed */
        
        // Check Number 1 ------------------------------------------------------------------------------------------------------------
        if ($this->request->postVar('receiver_email') != $account) {
        //handle the wrong business url
            $req .= "\n\nInvalid receiver_email";
            $this->doPaymentLog($payment, "Paypal Payment Log - Invalid receiver_email", $req);
            exit();
        }
        else{
            $req .= "\n\nPaypal Receiver Email OK";
        }
        
        // Check number 2 ------------------------------------------------------------------------------------------------------------
        if($obj = PaypalPayment::get()->exclude('ID', $payment->ID)->find('TxnRef', $this->request->postVar('txn_id'))){
        //check for duplicate txn_ids and custom in the database
            $req .= "\n\nDuplicate txn_id";
            $this->doPaymentLog($payment, "Paypal Payment Log - Duplicate txn_id", $req);
            exit();
        }
        else{
            $req .= "\n\nPaypal TXN ID OK";
        }
        
        // Check number 3 ------------------------------------------------------------------------------------------------------------
        if ($this->request->postVar('mc_gross') != $payment->Amount) {
            $req .= "\n\nIncorrect payment amount";
            $this->doPaymentLog($payment, "Paypal Payment Log - Incorrect payment amount", $req);
            exit();
        }
        else{
            $req .= "\n\nPaypal Payment Amount OK";
        }
        
        if ($this->request->postVar('mc_currency') != $payment->Currency) {
            $req .= "\n\nIncorrect payment currency";
            $this->doPaymentLog($payment, "Paypal Payment Log - Incorrect payment currency", $req);
            exit();
        }
        else{
            $req .= "\n\nPaypal Payment Currency OK";
        }
        
        // Check number 4 ------------------------------------------------------------------------------------------------------------
        if ($this->request->postVar('payment_status') != "Completed") {
            // Handle how you think you should if a payment is not complete yet, a few scenarios can cause a transaction to be incomplete
            if($this->request->postVar('payment_status') == "Pending" || $this->request->postVar('payment_status') == "Processed"){
                $payment->processPayment($data);
            }
            else if($this->request->postVar('payment_status') == "Voided" || $this->request->postVar('payment_status') == "Denied" || $this->request->postVar('payment_status') == "Expired"){
                $data['Message'] = 'Payment status '.strtolower($this->request->postVar('payment_status'));
                $payment->declinePayment($data);
                
                $req .= "\n\nPayment Status ".$this->request->postVar('payment_status');
                $this->doPaymentLog($payment, "Paypal Payment Log - Payment Status ".$this->request->postVar('payment_status'), $req);
                exit();
            }
            else{
                $data['Message'] = 'Payment status '.strtolower($this->request->postVar('payment_status'));
                $payment->failurePayment($data);
                
                $req .= "\n\nPayment Status ".$this->request->postVar('payment_status');
                $this->doPaymentLog($payment, "Paypal Payment Log - Payment Status ".$this->request->postVar('payment_status'), $req);
                exit();
            }
        }
        else{
            $payment->completePayment($data);
            $req .= "\n\nPaypal Payment Status OK";
        }
        
        $this->doPaymentLog($payment, "Paypal Payment Log - Paypal Payment OK!", $req);
        return 'VERIFIED';
    }
    
    function cancel() {
        $id = $this->request->param('ID');
        return $this->redirect(PaypalPayment::get()->byID($id)->RedirectLink());
    }
    
    function PreCompleteForm() {
        $fields = FieldList::create(
            HiddenField::create('ID', 'ID'),
            TextField::create('TxnRef', 'TXN Reference')
        );
        
        $actions = FieldList::create(FormAction::create("doPreComplete", _t('Paypal.BUTTONCOMPLETEDPAYMENT', 'I\'ve Completed Paypal Payment')));

        $validator = RequiredFields::create('TxnRef');

        return Form::create($this, 'PreCompleteForm', $fields, $actions, $validator);
    }

    function doPreComplete($data, $form) {
        try {
            $payment = PaypalPayment::get()->byID((int)$data['ID']);
            if($payment && $payment->Status == 'Incomplete') {
                $form->saveInto($payment);
                $payment->processPayment();
            }
            return _t('Paypal.SUCCESS_PRECOMPLETED_PAYMENT', 'You\'ve been pre-completed your paypal payment successfully');
        }
        catch(ValidationException $e) {
            $token = SecurityToken::inst();
            $link = $token->addToUrl(Controller::join_links('paypal', 'pre_complete', $data['ID']));
            $form->sessionMessage($e->getResult()->message(), 'error');
            return $this->redirect($link);
        }
    }

    function pre_complete() {
        if($this->request->isAjax() && SecurityToken::inst()->checkRequest($this->request)){
            $id = (int)$this->request->param('ID');
            $payment = PaypalPayment::get()->byID($id);
            if($payment){
                if($payment->Status == 'Incomplete') {
                    return $this->PreCompleteForm()->loadDataFrom($payment)->forAjaxTemplate();
                }
                else{
                    return _t('Paypal.SUCCESS_PRECOMPLETED_PAYMENT', 'You\'ve been pre-completed your paypal payment successfully');
                }
            }
        }

        return $this->httpError('404');
    }
    
    function complete(){
        if($payment = $this->validatePayment()){
            return $this->redirect($payment->RedirectLink());
        }
        
        return $this->httpError('404');
    }
    
    function validatePayment(){
        if(PaypalPayment::config()->validate_type == 'PDT'){
            if($custom = $this->request->getVar('cm')) {
                $params = explode('-', $custom);
                if(count($params) == 2) {
                    $payment = PaypalPayment::get()->byID($params[0]);
                    if($payment && $payment->AuthorisationCode == $params[1]) {
                        if($payment->Status == 'Incomplete'){
                            $this->processPDT($payment);
                        }
                        return $payment;
                    }
                }
            }
        }
        else{
            if($custom = $this->request->postVar('custom')) {
                $params = explode('-', $custom);
                if(count($params) == 2) {
                    $payment = PaypalPayment::get()->byID($params[0]);
                    if($payment && $payment->AuthorisationCode == $params[1]) {
                        if($payment->Status == 'Incomplete'){
                            $this->notify();
                        }
                        return $payment;
                    }
                }
            }
        }
        
        return false;
    }
    
    function processPDT($payment){
        // read the post from PayPal system and add 'cmd'
        $req = 'cmd=_notify-synch';
         
        $tx_token = $this->request->getVar('tx');
        $auth_token = PaypalPayment::config()->pdt_token;
        $url = PaypalPayment::config()->test_mode ? PaypalPayment::config()->test_url : PaypalPayment::config()->url;
        $account = PaypalPayment::config()->test_mode ? PaypalPayment::config()->test_account_email : PaypalPayment::config()->account_email;
        $curl_result = '';
        
        $req .= "&tx=$tx_token&at=$auth_token";
         
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
        //set cacert.pem verisign certificate path in curl using 'CURLOPT_CAINFO' field here,
        //if your server does not bundled with default verisign certificates.
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        //curl_setopt($ch, CURLOPT_HTTPHEADER, array("Host: $pp_hostname"));

        $req = str_replace("&", "\n\n", $req);  // Make it a nice list in case we want to email it to ourselves for reporting
        
        if(!($curl_result = curl_exec($ch))) {
            $req .= "\n\nConnection error - ".curl_error($ch);
            $this->doPaymentLog($payment, "Paypal Payment Log - Connection error", $req);
            curl_close($ch);
            exit();
        }
            
        curl_close($ch);

        // parse the data
        $lines = explode("\n", $curl_result);
        $post_data = array();
        if (strcmp($lines[0], "SUCCESS") == 0) {
            for($i=1; $i<count($lines);$i++){
                if(isset($lines[$i]) && $lines[$i]){
                    $req .= "\n\n".$lines[$i];
                    list($key,$val) = explode("=", $lines[$i]);
                    $post_data[urldecode($key)] = urldecode($val);
                }
            }
            
            $req .= "\n\nPaypal Verified OK";
        }
        else{
            $req .= "\n\nData not verified from Paypal!";
            $this->doPaymentLog($payment, "Paypal Payment Log - PDT data not verified", $req);
            exit();
        }

        $data = array(
            'TxnRef' => $post_data['txn_id']
        );
        
        /* CHECK THESE 4 THINGS BEFORE PROCESSING THE TRANSACTION, HANDLE THEM AS YOU WISH
          1. Make sure that business email returned is your business email
          2. Make sure there are no duplicate txn_id
          3. Make sure the payment amount/currency matches what you charge for items. (Defeat Price-Jacking)
          4. Make sure that the transaction payment status is completed */
        
        // Check Number 1 ------------------------------------------------------------------------------------------------------------
        if($post_data['receiver_email'] != $account) {
        //handle the wrong business url
            $req .= "\n\nInvalid receiver_email";
            $this->doPaymentLog($payment, "Paypal Payment Log - Invalid receiver_email", $req);
            exit();
        }
        else{
            $req .= "\n\nPaypal Receiver Email OK";
        }
        
        // Check number 2 ------------------------------------------------------------------------------------------------------------
        if($obj = PaypalPayment::get()->exclude('ID', $payment->ID)->find('TxnRef', $post_data['txn_id'])){
        //check for duplicate txn_ids and custom in the database
            $req .= "\n\nDuplicate txn_id";
            $this->doPaymentLog($payment, "Paypal Payment Log - Duplicate txn_id", $req);
            exit();
        }
        else{
            $req .= "\n\nPaypal TXN ID OK";
        }
        
        // Check number 3 ------------------------------------------------------------------------------------------------------------
        if ($post_data['mc_gross'] != $payment->Amount) {
            $req .= "\n\nIncorrect payment amount";
            $this->doPaymentLog($payment, "Paypal Payment Log - Incorrect payment amount", $req);
            exit();
        }
        else{
            $req .= "\n\nPaypal Payment Amount OK";
        }
        
        if ($post_data['mc_currency'] != $payment->Currency) {
            $req .= "\n\nIncorrect payment currency";
            $this->doPaymentLog($payment, "Paypal Payment Log - Incorrect payment currency", $req);
            exit();
        }
        else{
            $req .= "\n\nPaypal Payment Currency OK";
        }
        
        // Check number 4 ------------------------------------------------------------------------------------------------------------
        if ($post_data['payment_status'] != "Completed") {
            // Handle how you think you should if a payment is not complete yet, a few scenarios can cause a transaction to be incomplete
            if($post_data['payment_status'] == "Pending" || $post_data['payment_status'] == "Processed"){
                $payment->processPayment($data);
            }
            else if($post_data['payment_status'] == "Voided" || $post_data['payment_status'] == "Denied" || $post_data['payment_status'] == "Expired"){
                $data['Message'] = 'Payment status '.strtolower($post_data['payment_status']);
                $payment->declinePayment($data);
                
                $req .= "\n\nPayment Status ".$post_data['payment_status'];
                $this->doPaymentLog($payment, "Paypal Payment Log - Payment Status ".$post_data['payment_status'], $req);
                exit();
            }
            else{
                $data['Message'] = 'Payment status '.strtolower($post_data['payment_status']);
                $payment->failurePayment($data);
                
                $req .= "\n\nPayment Status ".$post_data['payment_status'];
                $this->doPaymentLog($payment, "Paypal Payment Log - Payment Status ".$post_data['payment_status'], $req);
                exit();
            }
        }
        else{
            $payment->completePayment($data);
            $req .= "\n\nPaypal Payment Status OK";
        }
        
        $this->doPaymentLog($payment, "Paypal Payment Log - Paypal Payment OK!", $req);
        return 'VERIFIED';
    }
    
    function PayPalForm() {
        Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
        $payment = PaypalPayment::get()->byID($this->request->param('ID'));
        
        // 1) Main Informations
        $fields = '';
        $order = DataObject::get_one($payment->Receipt()->TransactionType()->Object, "PaymentReceiptID = ".$payment->Receipt()->ID);
        $items = $order->Items();
        $member = $payment->Receipt()->Member();
        
        // 2) Main Settings
        $url = PaypalPayment::config()->test_mode ? PaypalPayment::config()->test_url : PaypalPayment::config()->url;
        $inputs['cmd'] = '_cart';
        $inputs['upload'] = '1';
        
        // 3) Items Informations
        
        $cpt = 0;
        foreach($items as $item) {
            $inputs['item_name_' . ++$cpt] = $item->ProductTitle;
            $inputs['item_number_' . $cpt] = $item->ProductCode;
            // item_number is unnecessary
            $inputs['amount_' . $cpt] = $item->UnitAmount;
            $inputs['quantity_' . $cpt] = $item->Quantity;
        }

        // 4) Payment Informations And Authorisation Code

        $inputs['business'] = PaypalPayment::config()->test_mode ? PaypalPayment::config()->test_account_email : PaypalPayment::config()->account_email;
        $inputs['custom'] = $payment->ID . '-' . $payment->AuthorisationCode; 
        // Add Here The Shipping And/Or Taxes
        $inputs['currency_code'] = $payment->Currency;

        // 5) Redirection Informations

        $inputs['cancel_return'] = Director::absoluteURL(Controller::join_links('paypal', 'cancel', $payment->ID));
        $inputs['return'] = Director::absoluteURL(Controller::join_links('paypal', 'complete', $payment->ID));
        if(PaypalPayment::config()->validate_type == 'IPN'){
            $inputs['notify_url'] = Director::absoluteURL(Controller::join_links('paypal', 'notify', $payment->ID));
        }
        
        $inputs['rm'] = '2';
        
        // 6) PayPal Pages Style Optional Informations
        if(SiteConfig::current_site_config()->CompanyLogo()->exists()){
            $inputs['cpp_header_image'] = urlencode(SiteConfig::current_site_config()->CompanyLogo()->getAbsoluteURL());
            $inputs['image_url'] = urlencode(SiteConfig::current_site_config()->CompanyLogo()->getAbsoluteURL());
        }
        
        // 7) Prepopulating Customer Informations

        $inputs['first_name'] = $member->FirstName;
        $inputs['last_name'] = $member->Surname;
        $inputs['address1'] = $member->Address;
        $inputs['address2'] = '';
        $inputs['city'] = $member->Suburb;
        $inputs['state'] = $member->State;
        $inputs['zip'] = $member->Postcode;
        $inputs['country'] = $member->Country;
        $inputs['email'] = $member->Email;

        // 8) Form Creation
        if(is_array($inputs) && count($inputs)) { 
            foreach($inputs as $name => $value) {
                $ATT_value = Convert::raw2att($value);
                $fields .= "<input type=\"hidden\" name=\"$name\" value=\"$ATT_value\" />";
            }
        }

        return <<<HTML
            <form id="PaymentForm" method="post" action="$url">
                $fields
                <input type="image" src="http://www.paypal.com/en_US/i/btn/x-click-but01.gif" name="submit" alt="Make payments with PayPal - its fast, free and secure!">
            </form>
            <script type="text/javascript">
                jQuery(document).ready(function() {
                    jQuery("input[type='submit']").hide();
                    jQuery("input[type='image']").hide();
                    jQuery('#PaymentForm').submit();
                });
            </script>
HTML;
    }

    function doPaymentLog($payment, $title, $body){
        PaypalPaymentLog::create_log($payment->ID, $title, $body);
        
        $from = "paypal-log@shareinfinity.com";
        $to = Email::config()->admin_email;
        $email = new Email($from, $to, $title, $body);
        $email->sendPlain();
    }
}
